#!/usr/bin/python
# -*- coding: latin1 -*-
"""

    Name: reader.py

    Author: Enrico Bortolazzi - Veia Progetti s.r.l.

    Comment: musicXML reader

    Version: 1.0

    Date: Fri Aug 17, 2007

------------------------------------------------------------------------

Copyright (c) 2007, Veia Progetti s.r.l. - http://www.veia.it

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

    Redistributions of source code must retain the above copyright notice,
    this list of conditions and the following disclaimer.

    Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in
    the documentation and/or other materials provided with the distribution.

    Neither name of Veia Progetti s.r.l. nor the name of its contributors
    may be used to endorse or promote products derived from this software
    without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""
import os
import xml.sax
from xml.sax import ContentHandler
from xml.sax.handler import EntityResolver

import musicXML
from _musicXML import *

class _MusicXmlHandler(ContentHandler):
    def __init__(self):
        ContentHandler.__init__(self)
        self._stack = []
        self.score = None

    def _buildElement(self, m_name, attributes):
        """ Create new element with attributes and value if needed """
        classname = xcapitalize(m_name)
        cls = getattr(musicXML, classname)
        attrs = dict([(name_mangle(str(a)),v) for a,v in attributes.items()])
        if cls._has_content:
            attrs['_content'] = ''
        return cls(**attrs)

    def _getFunctions(self, parent, m_name):
        """ return the insert function """
        getf = getattr(parent, 'get_%s' % m_name, None)
        if not getf:
            return None,None    
        setf = getattr(parent, 'set_%s' % m_name, None)
        if not setf:
            setf = getattr(parent, 'add_%s' % m_name, None)
        return getf, setf

    def _addElement(self, getf, setf, element):
        v = getf()
        if not v or (v and setf.__name__.startswith('add')):  #empty or add
            setf(element)
            return element
        return None    

    def _addIClass(self, getf, setf, cls_name, parent):
        v = getf()
        if not v or (v and setf.__name__.startswith('add')):  #empty or add
            ielement = getattr(parent, xcapitalize(cls_name))()
            setf(ielement)
            return ielement
        return None    

    def startElement(self, name, attributes):
        m_name = name_mangle(name)
        element = self._buildElement(m_name, attributes)
        if not self._stack:
            self.score = element
            self._stack.append(element)
            return
            
        parent = self._stack[-1]
        internal_cls = parent._name[0] == '_'
        getf,setf = self._getFunctions(parent, m_name)  #get insert function
        if getf:                
            if self._addElement(getf, setf, element):
                self._stack.append(element)
                return
            if not internal_cls:
                raise ValueError, 'Element <%s> already inserted, cannot be added'    
    
        if internal_cls and getf:
            cls_name = parent.__class__.__name__ #save the class name to construct another instance of the same
        else:
            cls_name = ''
        
        while parent._name[0] == '_':  #remove internal classe form stack
            self._stack.pop()
            parent = self._stack[-1]
                
        if cls_name:  #internal_cls and getf: try to add another class
            cls_getf,cls_setf = self._getFunctions(parent, cls_name)
            if not cls_getf:
                raise ValueError, 'Element <%s> cannot be added to %s (%s has no insert function)'\
                    % (name,parent._name,cls_name)
            ielement = self._addIClass(cls_getf, cls_setf, cls_name, parent)
            if not ielement:
                raise ValueError, 'Element <%s> cannot be added to %s (%s already existing)'\
                    % (name,parent._name,cls_name)
            self._stack.append(ielement)
            getf, setf = self._getFunctions(ielement, m_name)
            if not self._addElement(getf, setf, element):
                raise ValueError, 'Element <%s> cannot be added to %s' % (name,ielement._name)    
            self._stack.append(element)
            return

        if internal_cls: #internal_cls and not getf
            getf,setf = self._getFunctions(parent, m_name)  #get insert function
            if getf:                
                if self._addElement(getf, setf, element):
                    self._stack.append(element)
                    return
                if not internal_cls:
                    raise ValueError, 'Element <%s> already inserted, cannot be added'    
        
        for e in parent._elements:
            if e[0] != '_': continue
                                    
            cls_name = name_mangle(e)
            cls_getf,cls_setf = self._getFunctions(parent, cls_name)
            if not cls_getf:
                continue
            cls = getattr(parent, xcapitalize(cls_name))    
            icls_getf, icls_setf = self._getFunctions(cls, m_name)    
            if not icls_getf:
                continue
            ielement = self._addIClass(cls_getf, cls_setf, cls_name, parent)
            if not ielement:
                raise ValueError, 'Element <%s> cannot be added to %s (%s already existing)'\
                    % (name,parent._name,cls_name)
            self._stack.append(ielement)
            getf, setf = self._getFunctions(ielement, m_name)
            if not self._addElement(getf, setf, element):
                raise ValueError, 'Element <%s> cannot be added to %s' % (name,ielement._name)    
            self._stack.append(element)
            return
            
        raise ValueError, '%s has no insert function for element <%s>' % (parent._name, name)    
                        

    def endElement(self, name):
        while True:
            e = self._stack.pop()
            if e._name == str(name): break

    def characters(self, content):
        parent = self._stack[-1]
        if hasattr(parent, '_content'):
            parent._content += unicode(content)


class _MusicXMLEntityResolver(EntityResolver):
    
    def resolveEntity(self, publicId, systemId):
        if os.path.isfile(systemId):
            return systemId
        dir,fname = os.path.split(systemId)
        return os.path.abspath(os.path.dirname(__file__)+'/dtd/'+fname)


def read(filename_or_stream):
    handler = _MusicXmlHandler()

    parser = xml.sax.make_parser()
    parser.setContentHandler(handler)
    #avoid connection to the net to resolve entities
    parser.setEntityResolver(_MusicXMLEntityResolver())

    parser.parse(filename_or_stream, )
    return handler.score



